using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Xml;
using System.IO;
using System.Threading;

namespace Sio.Mdm.Utils
{
    /// <summary>
    /// Basic loggin functions for logging functions. This is not sophisticated logging
    /// system but it does provide extensions with <see cref="ILoggerWriter"/>
    /// </summary>
    public static class Logger
    {
        #region Declarations
        /// <summary>
        /// Enumeration describing possible logging message tpyes
        /// </summary>
        [Flags]
        public enum MessageType : int
        {
            /// <summary>
            /// Message type is unknown
            /// </summary>
            None,
            /// <summary>
            /// Message is informative
            /// </summary>
            Information,
            /// <summary>
            /// Message is only written for debug uilds
            /// </summary>
            Debug,
            /// <summary>
            /// Warrning message
            /// </summary>
            Warning = 0x0004,
            /// <summary>
            /// Exception has been thrown
            /// </summary>
            Exception = 0x0008,
            /// <summary>
            /// Fatal message type
            /// </summary>
            Fatal = 0x0010,
            /// <summary>
            /// Error type
            /// </summary>
            Error = 0x0020,
            /// <summary>
            /// Combination of all message types. Ussualy used in debug builds
            /// </summary>
            Diagnostic  = (Information | Debug | Warning | Exception | Fatal | Error),
            /// <summary>
            /// Combination for testing purposes. including all but information and debug
            /// </summary>
            Testing = (Exception | Fatal | Error | Warning),
            /// <summary>
            /// Minimal message combination. Used for deployment builds
            /// </summary>
            Minimal = (Exception | Fatal | Error),
            /// <summary>
            /// Same as <see cref="MessageType.Minimal"/>
            /// </summary>
            Errors = Minimal
        }
        /// <summary>
        /// Used for separate thread writting
        /// </summary>
        private struct MessageInfo
        {
            public MessageInfo(string msg, DateTime time, MessageType type)
            {
                Message = msg;
                Time = time;
                Type = type;
                WriteRetry = 0;
            }
            public string Message;
            public DateTime Time;
            public MessageType Type;
            public int WriteRetry;
        }
        #endregion
        
        #region Fields
        private static MessageType Filter = MessageType.Diagnostic;
        private const int MaxWriteRetry = 5;
        #endregion

        #region Constrution
        static Logger()
        {            
        }
        #endregion

        #region Properties

        #region Public

        #region FileName
        private static string _fileName;
        /// <summary>
        /// Get or set file name to use for logging
        /// </summary>
        public static string FileName
        {
            get
            {
                return _fileName;
            }
            set
            {
                if (value == _fileName)
                {
                    return;
                }
                
                _fileName = value;

                if (!String.IsNullOrEmpty(_fileName))
                {
                    Logger.Writers.Add(new LoggerDiskWriter(_fileName));
                }
            }
        }
        #endregion

        #region LogToConsole
        private static bool _logToConsole = true;
        /// <summary>
        /// Get or set wheater messages should be written in console
        /// </summary>
        public static bool LogToConsole
        {
            get
            {
                return _logToConsole;
            }
            set
            {
                _logToConsole = value;
            }
        }
        #endregion

        #region Writers
        private static List<ILoggerWriter> _writers;
        /// <summary>
        /// Get list of writers for logging
        /// </summary>
        public static List<ILoggerWriter> Writers
        {
            get
            {
                if (null == _writers)
                {
                    _writers = new List<ILoggerWriter>();
                }

                return _writers;
            }
        }
        #endregion

        #region UseSeparateThread
        private static bool _useSeparateThread = true;
        /// <summary>
        /// Get or set wheater writting is performed in separate thread those gaining
        /// performance. Note that when using this option <see cref="Destroy"/> must
        /// be called to ensure all messages are processed.
        /// </summary>
        public static bool UseSeparateThread
        {
            get
            {
                return _useSeparateThread;
            }
            set
            {
                _useSeparateThread = value;
            }
        }
        #endregion

        #region FlushTimeout
        private static int _flushTimeout = 100;
        /// <summary>
        /// Get or set timeout used to flush messages collected in log queue
        /// </summary>
        public static int FlushTimeout
        {
            get
            {
                return _flushTimeout;
            }
            set
            {
                _flushTimeout = value;
            }
        } 
        #endregion
        
        #endregion

        #region Private

        #region MessageQueue
        private static Queue<MessageInfo> _queue;
        private static Queue<MessageInfo> Queue
        {
            get
            {
                if (null == _queue)
                {
                    _queue = new Queue<MessageInfo>();
                }

                return _queue;
            }
        }
        #endregion

        #region MessageThread
        private static Thread _messageThread;        
        private static ManualResetEvent _exit;
        private static ManualResetEvent _shutdown;
        private static bool _exitThread = false;
        private static Thread MessageThread
        {
            get
            {
                if (null == _messageThread)
                {
                    _exit = new ManualResetEvent(false);
                    _shutdown = new ManualResetEvent(false);
                    _messageThread = new Thread(new ThreadStart(MessagePump));
                    _messageThread.IsBackground = true;
                    _messageThread.Start();
                }

                return _messageThread;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region ConfigureFromXML
        /// <summary>
        /// Configure logger from parsed XML config
        /// </summary>
        /// <param name="configXML">XML notation of config to use for logger</param>
        public static void ConfigureFromXML(string configXML)
        {
            try
            {
                XmlDocument doc = new XmlDocument();

                doc.LoadXml(configXML);

                XmlNode loggingNode = ConfigHelper.GetNodeByName(doc, "logging");

                string filePath = ConfigHelper.GetAttributeValueByName(loggingNode, "path");

                if (!String.IsNullOrEmpty(filePath))
                {
                    FileName = filePath;
                }

                string logToConsole = ConfigHelper.GetAttributeValueByName(loggingNode, "console");

                if (!String.IsNullOrEmpty(logToConsole))
                {
                    if (!bool.TryParse(logToConsole, out _logToConsole))
                    {
                        _logToConsole = true;
                    }
                }

                string flushTimeout = ConfigHelper.GetAttributeValueByName(loggingNode, "flushTimeout");

                if (!String.IsNullOrEmpty(flushTimeout))
                {
                    if (!int.TryParse(flushTimeout, out _flushTimeout))
                    {
                        _flushTimeout = 100;
                    }
                }

                XmlNode typesNode = ConfigHelper.GetNodeByName(loggingNode, "types");

                if (typesNode != null)
                {
                    Filter = MessageType.None;

                    foreach (XmlNode childNode in typesNode.ChildNodes)
                    {
                        if (childNode is XmlComment)
                        {
                            continue;
                        }

                        MessageType type;

                        try
                        {
                            type = (MessageType)Enum.Parse(typeof(MessageType), childNode.Name, true);
                        }
                        catch
                        {
                            continue;
                        }

                        if (String.Compare(ConfigHelper.GetAttributeValueByName(childNode, "enabled"), "true", true) == 0)
                        {
                            Filter |= type;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Logger configuration is invalid", ex);
            }
        }
        #endregion

        #region GetXMLConfig
        /// <summary>
        /// Return current configuration in XML format
        /// </summary>
        /// <returns>XML string of curent configuration</returns>
        public static string GetXMLConfig()
        {
            StringBuilder builder = new StringBuilder();

            if (String.IsNullOrEmpty(FileName))
            {
                builder.Append("\t<logging>\n");
            }
            else
            {
                builder.AppendFormat("\t<logging path=\"{0}\">\n", _fileName);
            }

            builder.Append("\t\t<types>\n");
            builder.AppendFormat("\t\t<debug enabled=\"{0}\" />\n", (Filter & MessageType.Debug) > 0);
            builder.AppendFormat("\t\t<information enabled=\"{0}\" />\n", (Filter & MessageType.Information) > 0);
            builder.AppendFormat("\t\t<warning enabled=\"{0}\" />\n", (Filter & MessageType.Warning) > 0);
            builder.AppendFormat("\t\t<error enabled=\"{0}\" />\n", (Filter & MessageType.Error) > 0);
            builder.AppendFormat("\t\t<fatal enabled=\"{0}\" />\n", (Filter & MessageType.Fatal) > 0);
            builder.AppendFormat("\t\t<exception enabled=\"{0}\" />\n", (Filter & MessageType.Exception) > 0);
            builder.Append("\t\t</types>\n");
            builder.Append("\t</logging>\n");

            return builder.ToString();
        }
        #endregion

        #region Initialize, Destroy
        /// <summary>
        /// Initialize logger with default values
        /// </summary>
        public static void Initialize()
        {
            if (UseSeparateThread)
            {
                Queue.ToString();
                MessageThread.ToString();
            }
        }
        /// <summary>
        /// Stop loggin and remove all resources used
        /// </summary>
        public static void Destroy()
        {
            if (_messageThread != null)
            {
                _exitThread = true;

                _shutdown.Set();
                _exit.WaitOne();
            }
        }
        #endregion

        #region WriteInformation
        /// <summary>
        /// Write message of <see cref="MessageType.Information"/>
        /// </summary>
        /// <param name="message">Message string with additional formatting</param>
        /// <param name="args">Formatting parameters</param>
        public static void WriteInformation(string message, params object[] args)
        {
            ConsoleColor color = Console.ForegroundColor;
            
            Console.ForegroundColor = ConsoleColor.White;
            WriteMessage(MessageType.Information, message, args);
            Console.ForegroundColor = color;
        }
        #endregion

        #region WriteDebug
        /// <summary>
        /// Write message of <see cref="MessageType.Debug"/>
        /// </summary>
        /// <param name="message">Message string with additional formatting</param>
        /// <param name="args">Formatting parameters</param>        
        public static void WriteDebug(string message, params object[] args)
        {
            ConsoleColor color = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Green;
            WriteMessage(MessageType.Debug, message, args);
            Console.ForegroundColor = color;
        }
        #endregion

        #region WriteWarning
        /// <summary>
        /// Write message of <see cref="MessageType.Warning"/>
        /// </summary>
        /// <param name="message">Message string with additional formatting</param>
        /// <param name="args">Formatting parameters</param>
        public static void WriteWarning(string message, params object[] args)
        {
            ConsoleColor color = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Yellow;
            WriteMessage(MessageType.Warning, message, args);
            Console.ForegroundColor = color;
        }
        #endregion

        #region WriteError
        /// <summary>
        /// Write message of <see cref="MessageType.Error"/>
        /// </summary>
        /// <param name="message">Message string with additional formatting</param>
        /// <param name="args">Formatting parameters</param>
        public static void WriteError(string message, params object[] args)
        {
            ConsoleColor color = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Red;
            WriteMessage(MessageType.Error, message, args);
            Console.ForegroundColor = color;
        }
        #endregion

        #region WriteFatal
        /// <summary>
        /// Write message of <see cref="MessageType.Fatal"/>
        /// </summary>
        /// <param name="message">Message string with additional formatting</param>
        /// <param name="args">Formatting parameters</param>
        public static void WriteFatal(string message, params object[] args)
        {
            ConsoleColor color = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Red;
            WriteMessage(MessageType.Fatal, message, args);
            //Console.Beep();
            Console.ForegroundColor = color;
        }
        #endregion

        #region WriteException
        /// <summary>
        /// Write message of <see cref="MessageType.Exception"/>
        /// </summary>        
        /// <param name="ex">Exception to log</param>
        public static void WriteException(_Exception ex)
        {
            ConsoleColor color = Console.ForegroundColor;

            Console.ForegroundColor = ConsoleColor.Red;
            WriteMessage(MessageType.Exception, String.Format("{0}", ex.ToString()));
            Console.ForegroundColor = color;
        }
        #endregion

        #endregion

        #region Private

        #region WriteMessage
        private static void WriteMessage(MessageType type, string message, params object[] args)
        {
            if ((Filter & type) > 0)
            {
                if (String.IsNullOrEmpty(message))
                {
                    message = "Format string is empty";
                }

                DateTime timeStamp = DateTime.Now;

                if (LogToConsole)
                {
                    Console.WriteLine(String.Format("[{1}] {0} {2}", timeStamp, type, String.Format(message, args)));
                }

                if (UseSeparateThread)
                {
                    Queue.Enqueue(new MessageInfo(String.Format(message, args), timeStamp, type));                    
                }
                else
                {
                    // Relay to all writers
                    foreach (ILoggerWriter writer in Writers)
                    {
                        writer.WriteMessage(type, timeStamp, String.Format(message, args));
                    }
                }
            }
        }
        #endregion

        #region MessagePump
        private static void MessagePump()
        {
            while (true)
            {
                if (Queue.Count == 0 && _exitThread)
                {
                    break;
                }

                while(Queue.Count > 0)
                {                    
                    MessageInfo message = Queue.Dequeue();

                    // Relay to all writers
                    foreach (ILoggerWriter writer in Writers)
                    {
                        try
                        {
                            writer.WriteMessage(message.Type, message.Time, message.Message);
                        }
                        catch(Exception)
                        {
                        }
                    }
                }

                _shutdown.WaitOne(_flushTimeout, false);
            }

            _exit.Set();
        }
        #endregion

        #endregion

        #endregion
    }
}
